﻿
//	------------------------------------------------------------------------------
//                                       _                          
//              _ __ ___   ___ _ __ ___ | |__  _ __ __ _ _ __   ___ 
//             | '_ ` _ \ / _ \ '_ ` _ \| '_ \| '__/ _` | '_ \ / _ \
//             | | | | | |  __/ | | | | | |_) | | | (_| | | | |  __/
//             |_| |_| |_|\___|_| |_| |_|_.__/|_|  \__,_|_| |_|\___|
//                                                     
//	------------------------------------------------------------------------------

#region - License -
//
//	------------------------------------------------------------------------------                                                   
//	         Copyright (c) 2010 Peter Vyvey (peter.vyvey@chiliware.be)
//	------------------------------------------------------------------------------
//
//
//	This file is part of CHILIWARE MEMBRANE.
//
//	CHILIWARE MEMBRANE is free software: you can redistribute it and/or modify
//	it under the terms of the GNU Lesser General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	CHILIWARE MEMBRANE is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU Lesser General Public License for more details.
//
//	You should have received a copy of the GNU Lesser General Public License
//	along with CHILIWARE MEMBRANE.  If not, see <http://www.gnu.org/licenses/>.
//
//
#endregion

#region - Changes -

//	peter.vyvey@chiliware.be     2010-08-03      Created.

#endregion

#region - Using -

using System;
using System.Data.Objects.DataClasses;
using System.Data.Objects;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;

#endregion

namespace Chiliware.Membrane.Extension
{
	#region - DataObjectExtension -

	/// <summary>
	/// Extension methods to handle extra functionality on data object and entities.
	/// </summary>
	public static class DataObjectExtension
	{
		#region - Constants & static fields -

		private const string DEFAULT_INCLUDE_SEPERATOR = ".";

		#endregion

		#region - Nested class : EntityFrameworkHelper -

		internal static class EntityFrameworkHelper
		{
			internal static void CollectRelationalMembers(Expression exp, IList<PropertyInfo> members)
			{
				if (exp.NodeType == ExpressionType.Lambda)
				{
					// At root, explore body:  
					CollectRelationalMembers(((LambdaExpression)exp).Body, members);
				}
				else if (exp.NodeType == ExpressionType.MemberAccess)
				{
					MemberExpression mexp = (MemberExpression)exp;
					CollectRelationalMembers(mexp.Expression, members);
					members.Add((PropertyInfo)mexp.Member);
				}
				else if (exp.NodeType == ExpressionType.Call)
				{
					MethodCallExpression cexp = (MethodCallExpression)exp;

					if (cexp.Method.IsStatic == false)
						throw new InvalidOperationException("Invalid type of expression.");

					foreach (var arg in cexp.Arguments)
						CollectRelationalMembers(arg, members);
				}
				else if (exp.NodeType == ExpressionType.Parameter)
				{
					// Reached the toplevel:  
					return;
				}
				else
				{
					throw new InvalidOperationException("Invalid type of expression.");
				}
			}
		}

		#endregion

		#region - Public static methods -

		/// <summary>
		/// Gets the <see cref="ObjectContext"/> for a entity.
		/// </summary>
		/// <typeparam name="T">The expected type of <see cref="ObjectContext"/>.</typeparam>
		/// <param name="entity">The entity</param>
		/// <returns>The instance <see cref="ObjectContext"/> of the entity.</returns>
		/// <exception cref="System.Exception">Throw if the entity has no relationships, the entity is detached or if the actual <see cref="ObjectContext"/> cannot be casted to type of T.</exception>
		public static T GetContext<T>(this IEntityWithRelationships entity)
			where T : ObjectContext
		{
			entity.CatchNullArgument("entity");

			// Does some relationship exist ?
			RelationshipManager relationshipManager = entity.RelationshipManager;
			var relatedEnd = relationshipManager.GetAllRelatedEnds().FirstOrDefault();
			if (null == relatedEnd)
			{
				throw new Exception("No relationships found");
			}

			// Is the entity DETACHED ? If so there is no ObjectQuery.
			ObjectQuery query = relatedEnd.CreateSourceQuery() as ObjectQuery;
			if (null == query)
			{
				throw new Exception("The entity is detached");
			}

			// Try to cast the context.
			T context = query.Context as T;
			if (default(T) == context)
			{
				throw new Exception(string.Format("The entity is not part of a context of type {0}", typeof(T).AssemblyQualifiedName));
			}

			return context;
		}

		/// <summary>
		/// Get the entity for a Entity Framework reference.
		/// </summary>
		/// <typeparam name="T">The entity type.</typeparam>
		/// <param name="reference">The reference.</param>
		/// <returns>The entity.</returns>
		public static T GetEntity<T>(this EntityReference<T> reference)
			where T : EntityObject
		{
			if (!reference.IsLoaded)
			{
				reference.Load();
			}
			return reference.Value;
		}

		/// <summary>
		/// Loads the set of entities via the Entity Framework.
		/// </summary>
		/// <typeparam name="T">The type of the entities.</typeparam>
		/// <param name="collection">The collection to load.</param>
		/// <returns>A collection loaded with the entities.</returns>
		public static EntityCollection<T> LoadEntities<T>(this EntityCollection<T> collection)
			where T : EntityObject
		{
			if (!collection.IsLoaded)
			{
				collection.Load();
			}
			return collection;
		}

		/// <summary>  
		/// Specifies the related objects to include in the query results using a lambda expression mentioning the path members.  
		/// </summary>  
		/// <returns>A new System.Data.Objects.ObjectQuery{T} with the defined query path.</returns>  
		public static ObjectQuery<T> IncludeEntities<T>(this ObjectQuery<T> query, Expression<Func<T, object>> path)
		{
			// Retrieve member path.
			List<PropertyInfo> members = new List<PropertyInfo>();
			EntityFrameworkHelper.CollectRelationalMembers(path, members);

			// Build string path.
			StringBuilder sb = new StringBuilder();
			string separator = string.Empty;

			members.ForEach(member =>
			{
				sb.Append(separator);
				sb.Append(member.Name);
				separator = DataObjectExtension.DEFAULT_INCLUDE_SEPERATOR;
			});

			// Apply INCLUDE.
			return query.Include(sb.ToString());
		}

		#endregion
	}

	#endregion
}
